package com.seed.bpmn.provider.activiti.service;

import com.seed.bpmn.engine.IBPMNRepositoryService;
import com.seed.bpmn.engine.common.constants.BpmnConstants;
import com.seed.bpmn.engine.common.util.Empty;
import com.seed.bpmn.engine.common.util.Page;
import com.seed.bpmn.engine.dto.FlowDTO;
import com.seed.bpmn.engine.dto.NodeDTO;
import com.seed.bpmn.engine.dto.ProcessDefinitionDTO;
import com.seed.bpmn.provider.activiti.common.util.ActivitiUtil;
import com.seed.bpmn.provider.activiti.common.util.DTOUtil;
import net.sf.json.JSONObject;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.task.Task;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.util.*;
import java.util.zip.ZipInputStream;

import static com.seed.bpmn.engine.common.constants.BpmnConstants.ELEMENT_START_EVENT_ID;

@Component("bpmnRepositoryService")
public class BPMNRepositoryServiceImpl implements IBPMNRepositoryService {
    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private ActivitiUtil activitiUtil;

    @Override
    public ProcessDefinitionDTO deploy(String name, String fileName, String category, InputStream inputStream) throws Exception {
        //流程部署（发布）
        DeploymentBuilder b = this.repositoryService.createDeployment().name(name).category(category);
        Deployment d = null;

        String extension = FilenameUtils.getExtension(fileName);
        if("zip".equals(extension) || "bar".equals(extension)|| "rar".equals(extension)){
            ZipInputStream zip = new ZipInputStream(inputStream);
            d = b.addZipInputStream(zip).deploy();
        }else{
            d = b.addInputStream(fileName, inputStream).deploy();
        }

        //获取发布成功的流程模版
        ProcessDefinition pd = this.repositoryService.createProcessDefinitionQuery().deploymentId(d.getId()).singleResult();
        return DTOUtil.getProcessDefinitionDTO(pd);
    }

    @Override
    public ProcessDefinitionDTO getProcessDefinition(String id) throws Exception {
        ProcessDefinition pd = this.repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();
        return DTOUtil.getProcessDefinitionDTO(pd);
    }

    @Override
    public List<ProcessDefinitionDTO> findProcessDefinitionByPage(String name, String key, Page page) throws Exception {
        //创建流程模版查询对象
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();

        //----封装查询条件----------
        if(!Empty.isEmpty(name)){
            query.processDefinitionNameLike("%"+name+"%");
        }

        if(!Empty.isEmpty(key)){
            query.processDefinitionKey(key);
        }
        //--------------------------

        //排序设置
        query.orderByProcessDefinitionKey().asc().orderByProcessDefinitionVersion().desc();

        //分页信息设置
        page.setTotalObjects((int)query.count());
        List<ProcessDefinition> dlist = query.listPage(page.getFirstResult(), page.getPageIndex()*page.getPageSize());

        List<ProcessDefinitionDTO> list = new ArrayList<>();
        for(ProcessDefinition d : dlist){
            list.add(DTOUtil.getProcessDefinitionDTO(d));
        }

        return list;
    }

    @Override
    public ProcessDefinitionDTO findProcessDefinitionByKey(String key) throws Exception {
        //根据KEY获取最新的激活状态的流程定义
        ProcessDefinitionQuery query = this.repositoryService.createProcessDefinitionQuery();
        List<ProcessDefinition> list = query.processDefinitionKey(key).active().orderByProcessDefinitionVersion().desc().listPage(0, 1);
        if(!Empty.isEmpty(list)){
            return DTOUtil.getProcessDefinitionDTO(list.get(0));
        }else{
            return null;
        }
    }

    @Override
    public NodeDTO getNodeByProcessDefinitionKey(String processDefinitionKey, String nodeId) throws Exception {
        NodeDTO node = null;
        if(Empty.isNotEmpty(processDefinitionKey)) {
            ProcessDefinitionDTO d = this.findProcessDefinitionByKey(processDefinitionKey);
            if(Empty.isNotEmpty(d)){
                node = this.getNodeByProcessDefinitionId(d.getId(),nodeId);
            }
        }
        return node;
    }

    @Override
    public List<NodeDTO> getNodeByProcessDefinitionId(String processDefinitionId) throws Exception {
        try{
            BpmnModel model = repositoryService.getBpmnModel(processDefinitionId);
            Collection<UserTask> list = model.getMainProcess().findFlowElementsOfType(UserTask.class);
            List<NodeDTO> nodes = new ArrayList<NodeDTO>();
            for(UserTask t : list){
                NodeDTO node = new NodeDTO();
                node.setId(t.getId());
                node.setName(t.getName());
                String documentation = t.getDocumentation();
                if(Empty.isNotEmpty(documentation)){
                    JSONObject data = JSONObject.fromObject(documentation);
                    //封装流程节点配置
                    node = DTOUtil.getNodeDTO(data, node);
                }
                nodes.add(node);
            }
            return nodes;
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public NodeDTO getNodeByProcessDefinitionId(String processDefinitionId, String nodeId) throws Exception {
        RepositoryServiceImpl repositoryServiceImpl = (RepositoryServiceImpl)repositoryService;

        //流程定义实体类,能得到流程的环节
        ProcessDefinitionEntity pde = (ProcessDefinitionEntity) repositoryServiceImpl.getDeployedProcessDefinition(processDefinitionId);

        NodeDTO node = new NodeDTO();
        String documentation;

        if(Empty.isNotEmpty(nodeId)){
            //通过流程环节id，可以得到流程环节
            ActivityImpl a = pde.findActivity(nodeId);
            documentation = (String) a.getProperty(BpmnConstants.ELEMENT_DOCUMENTATION);

            node.setId(a.getId());
            node.setName(a.getProperty(BpmnConstants.ATTRIBUTE_NAME).toString());
        }else{
            //通过流程环节id，可以得到流程环节
            ActivityImpl activity = pde.findActivity(BpmnConstants.ELEMENT_START_EVENT_ID);

            //得到流程环节出去的线
            List<PvmTransition> pts = activity.getOutgoingTransitions();
            PvmTransition t = pts.get(0);
            PvmActivity d = t.getDestination();

            //获取流程节点documentation上配置的参数
            documentation = (String) d.getProperty(BpmnConstants.ELEMENT_DOCUMENTATION);

            node.setId(d.getId());
            node.setName(d.getProperty(BpmnConstants.ATTRIBUTE_NAME).toString());
        }

        if(Empty.isNotEmpty(documentation)){
            JSONObject data = JSONObject.fromObject(documentation);
            //封装流程节点配置
            node = DTOUtil.getNodeDTO(data,node);
        }

        return node;
    }

    @Override
    public NodeDTO getNodeByTaskId(String taskId) throws Exception {
        Task t = this.taskService.createTaskQuery().taskId(taskId).singleResult();
        if(Empty.isNotEmpty(t)){
            return this.getNodeByProcessDefinitionId(t.getProcessDefinitionId(),t.getTaskDefinitionKey());
        }else {
            return null;
        }
    }

    @Override
    public List<FlowDTO> getNextTransitions(String processDefinitionId, String taskId) throws Exception {

        String nodeId = ELEMENT_START_EVENT_ID;

        //当任务id不为空时
        if(!Empty.isEmpty(taskId)){
            Task task = this.taskService.createTaskQuery().taskId(taskId).singleResult();
            nodeId = task.getTaskDefinitionKey();
            processDefinitionId = task.getProcessDefinitionId();
        }

        return getNextTransitions(processDefinitionId, taskId, nodeId);
    }

    @Override
    public List<FlowDTO> getNextTransitions(String processDefinitionId, String taskId, String nodeId) throws Exception {
        List<FlowDTO> flows = new ArrayList<FlowDTO>();

        if(Empty.isNotEmpty(processDefinitionId) && Empty.isNotEmpty(nodeId)){
            //流程定义实体类,能得到流程的环节
            RepositoryServiceImpl repositoryServiceImpl = (RepositoryServiceImpl) this.repositoryService;
            ProcessDefinitionEntity pde = (ProcessDefinitionEntity) repositoryServiceImpl.getDeployedProcessDefinition(processDefinitionId);

            //通过流程环节id，可以得到流程环节
            ActivityImpl activity = pde.findActivity(nodeId);

            //得到流程环节出去的线
            List<PvmTransition> pts = activity.getOutgoingTransitions();

            //封装下一流程相关数据
            activitiUtil.getNextTransitionsProperties(taskId,pts,flows,null);
        }

        return flows;
    }

    @Override
    public InputStream getProcessDefinitionXml(String processDefinitionId) throws Exception {
        return this.getProcessDefinitionById(processDefinitionId,"xml");
    }

    @Override
    public InputStream getProcessDefinitionImage(String processDefinitionId) throws Exception {
        return this.getProcessDefinitionById(processDefinitionId,"image");
    }

    @Override
    public InputStream getProcessDefinitionById(String processDefinitionId, String type) throws Exception {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId);
        ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
        String resourceName = "";
        if("image".equalsIgnoreCase(type)) {
            resourceName = processDefinition.getDiagramResourceName();
        }else if("xml".equalsIgnoreCase(type)) {
            resourceName = processDefinition.getResourceName();
        }
        InputStream is = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
        return is;
    }

    @Override
    public InputStream getProcessDefinitionByProcessInstanceId(String processInstanceId, String type) throws Exception {
        HistoricProcessInstance p = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        return this.getProcessDefinitionById(p.getProcessDefinitionId(),type);
    }

    @Override
    public void deleteProcessDefinition(List<String> ids) throws Exception {
        for(String id : ids){
            repositoryService.deleteDeployment(id,true);
        }
    }

    @Override
    public void updateProcessDefinition(List<String> ids, int type) throws Exception {
        for(String id : ids){
            if(type == 1){
                repositoryService.activateProcessDefinitionById(id);
            }else{
                repositoryService.suspendProcessDefinitionById(id);
            }
        }
    }

    @Override
    public List<Map<String, Object>> traceProcess(String processInstanceId) throws Exception {

        //执行实例
        HistoricProcessInstance p = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        String processDefinitionId = p.getProcessDefinitionId();

        Set<String> executions = new HashSet<String>();

        if(Empty.isNotEmpty(p.getEndTime())){
            executions.add(p.getEndActivityId());
        }else{
            List<Execution> es = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list();
            for(Execution e : es){
                if(Empty.isNotEmpty(e.getActivityId())){
                    executions.add(e.getActivityId());
                }
            }
        }

        //流程定义实体类,能得到流程的环节
        RepositoryServiceImpl repositoryServiceImpl = (RepositoryServiceImpl) this.repositoryService;
        ProcessDefinitionEntity pde = (ProcessDefinitionEntity) repositoryServiceImpl.getDeployedProcessDefinition(processDefinitionId);

        //获得当前任务的所有节点
        List<ActivityImpl> alist = pde.getActivities();

        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

        for (ActivityImpl a : alist) {
            Map<String, Object> map = new HashMap<String, Object>();

            //当前节点
            boolean current = false;
            for(String activityId : executions){
                if(a.getId().equals(activityId)) {
                    current = true;
                    break;
                }
            }
            map.put("current",current);

            map.put("id",a.getId());
            map.put("x", a.getX());
            map.put("y", a.getY());
            map.put("w", a.getWidth());
            map.put("h", a.getHeight());

            list.add(map);
        }

        return list;
    }
}
